import React, { lazy, Suspense } from "react";
import { Routes, Route, NavLink, Link, Navigate } from "react-router-dom";
import PageRouter from "./views/pages/PageRouter";
import SonRouter from "./views/pages/sonRouter";
import Page2 from "./views/pages/Page2";
import Page3 from "./views/pages/Page3";
import StoreComponent from "./StoreComponent";
import "./App.css";
import "./Son";
import Son from "./Son";
import Son2 from "./Son2";
import CRUD from "./CRUD";
import FunCom from "./FunCom";
import Hoc from "./Hoc";
import Optimization from "./OptimizationCom";

//异步路由
const LazyPage = lazy(() => import("./views/pages/Lazy"));

//Optimization为函数组件，使用React.memo包裹，当props、state不变时，不会重新渲染dom
const MemoOptimization = React.memo(Optimization);

let HocSon2 = Hoc(Son2);

//祖先传递数据的context组件
export const Context1 = React.createContext();
// class语法写组件 必须要有render方法
class App extends React.Component {
  /**PureComponent与component区别：
   *1.component每次setState都会重新渲染dom，哪怕每次修改后的值是一样的
   *2.PureComponent会进行浅比较，如果前后两次state或者props相等，则不会重新渲染dom,这样导致了一个问题 数组进行push、pop等操作后，数组相等，虽然数组内容变了，但是以为对象比较的是内存地址，内存地址不变，所以PureComponent不会重新渲染dom
   */
  // constructor(props) {
  //   super(props);
  //   //使用constructor方法初始化state
  //   this.state = {
  //     index: -1,
  //   };
  // }
  //创建一个ref对象
  divRef = React.createRef();
  sonRef = React.createRef();
  //新写法  直接写在外面
  state = {
    menuList: [
      '  事件绑定{" "} ',
      "  数据双向绑定 ",
      "  条件渲染、 列表渲染 ",
      "表单绑定",
      "props",
      "列表增删改查",
      "ref和context(类似vue中的provide/inject)",
      "函数组件和hook",
      "高阶组件",
      "性能优化",
      "react-router",
      "状态管理",
    ],
    navList: [
      {
        name: "react-route路由",
        path: "/router",
      },
      {
        name: "react-route子路由",
        path: "/router/son-router",
      },
      {
        name: "page3动态路由",
        path: "/page3/3",
      },
      {
        name: "page2权限路由",
        path: "/page2",
      },
      {
        name: "异步路由",
        path: "/lazy",
      },
    ],
    arr: [1],
    index: 11,
    info: {
      nickName: "张三",
      age: 18,
    },
    hobbies: [
      {
        name: "篮球",
        value: 1,
        checked: false,
      },
      {
        name: "足球",
        value: 2,
        checked: false,
      },
      {
        name: "羽毛球",
        value: 3,
        checked: false,
      },
    ],
    formData: {
      nickName: "",
      hobby: [3],
    },
    message: "父组件传递的数据",
    propSon2Msg: "",
  };

  render() {
    const _token = localStorage.getItem("token");
    return (
      <div
        className="App"
        // onClick={() => {
        //   alert("点击了顶层div");
        // }}
      >
        <div className="nav">
          {this.state.menuList.map((item, index) => {
            return (
              <h3 key={item} onClick={this.handleExpand.bind(this, index)}>
                {index + 1}、 {item}
              </h3>
            );
          })}
        </div>
        <div className="content">
          <div
            className="event-box"
            style={{
              height: this.state.index === 0 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            {/* 事件绑定 on+首字母大写的事件名 且值为一个包裹在{}中的方法 */}
            更改this的方法： 1.给方法通过bind进行绑定 2.匿名箭头函数
            3.绑定的方法使用箭头函数进行声明
            <h3> 1.通过bind进行绑定</h3>
            <button onClick={this.handleClick.bind(this)}>按钮</button>
            <div>
              <h3> 2.匿名箭头函数</h3>
              箭头函数:
              <span>
                箭头函数定义时的上下文决定了它如何捕获的this值。箭头函数不会创建自己的this上下文，而是从外围作用域（函数或全局作用域）继承this值
              </span>
            </div>
            <button onClick={() => this.handleClick()}>按钮</button>
            <h3> 3.绑定的方法使用箭头函数进行声明</h3>
            <button onClick={this.handleArrowFnClick}>按钮</button>
            <hr />
            <h2>事件传递参数</h2>
            <h3>未传参数，默认第一个参数为事件对象</h3>
            <button onClick={this.handleEventClick.bind(this)}>按钮</button>
            <h3>传了参数，默认最后一个参数为事件对象</h3>
            <button onClick={this.handleParamsClick.bind(this, 1, 2)}>
              按钮
            </button>
            <a href="###啦啦啦啦" onClick={this.handleEventClick.bind(this)}>
              a标签
            </a>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 1 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            <div>
              数据双向绑定 异步更新 修改后直接打印
              会是旧的值，需要在setState传入第二次函数获取最新
            </div>
            定义方式：1.constructor中的state进行定义
            <div>
              <h3>1.通过state定义</h3>
              <div className="code-block">
                {`              
constructor(props) {
    super(props);
    this.state = {
      nickName: "张三",
 };
}
  
  使用方式：通过this.state.属性名进行使用
  修改方式：通过this.setState({属性名：属性值})进行修改
  this.setState({
      index: 0,//基础数据类型 直接修改
      info: {
        ...this.state.info, //对象 浅合并info
        nickName: e.target.value,
      },
    });
  
  `}
              </div>
            </div>
            定义方式2：直接写在组件里面
            <div>
              <input
                type="text"
                value={this.state.info.nickName}
                onChange={this.handleInputChange.bind(this)}
              />
              名字{this.state.info.nickName}
              年龄{this.state.info.age}
            </div>
            <div>
              数组{this.state.arr}
              <button onClick={this.handleArrAdd}>添加1</button>
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 2 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            條件渲染
            <div className="code-block">
              {`
                index===-1? "展开" : "收起"
              `}
            </div>
            列表渲染
            <div className="code-block">
              {`
              //数组直接map进行渲染，类似于vue的v-for
          {this.state.menuList.map((item, index) => {
            return (
            //返回的是包含html结构的元素
              <h3 onClick={this.handleExpand.bind(this, index)}>
                {index + 1}、 {item}
              </h3>
            );
          })}
                `}
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 3 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            表单绑定
            <div>
              <span>姓名</span>{" "}
              <input
                type="text"
                value={this.state.formData.nickName}
                onInput={(e) => {
                  this.setState({
                    formData: {
                      ...this.state.formData,
                      nickName: e.target.value,
                    },
                  });
                }}
              />
            </div>
            <div>
              <span>爱好</span>
              {this.state.hobbies.map((item) => {
                return (
                  <div key={item.name}>
                    <input
                      type="checkbox"
                      checked={this.state.formData.hobby.includes(item.value)}
                      name={item.name}
                      value={item.value}
                      onChange={this.handleCheckChange.bind(this)}
                    />
                    <label htmlFor={item.name}>{item.name}</label>
                  </div>
                );
              })}
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 4 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            一切写在组件上的属性和子节点都被规划为了props 1.直接传递数据 2. slot
            会作为props.children传递
            <div className="code-block" a="1" b="2">
              {`
          <Son>
             <div>我是slot</div>
          </Son>

          
          `}
            </div>
            作用域插槽：子组件传递给父组件的数据 使用slotContent传递一个方法
            <Son
              message={this.state.message}
              slotContent={(scope) => {
                return <div style={{ background: "green" }}>{scope}</div>;
              }}
              changeMessage={this.handleMessageBySon}
              propMessage={this.propMessage}
            >
              <div style={{ background: "red" }}>我是slot</div>
            </Son>
            <hr />
            <h3>子组件修改父组件的数据</h3>
            使用父组件通过props传递子组件一个方法，子组件进行调用并传递参数进行处理
            <div className="code-block">
              {`<Son changeMessage={this.handleMessageBySon}></Son>`}
            </div>
            <Son2 propSon2Msg={this.state.propSon2Msg}></Son2>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 5 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            <CRUD></CRUD>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 6 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            1.类组件使用currentRef后的current获取DOM节点
            <div ref={this.divRef}>refDom</div>
            <div className="code-block">
              {`
              let divRef = React.createRef();
              <div ref={divRef}></div>
                //在组件挂载后获取ref的dom
            componentDidMount() {
                console.log(divRef.current);
              }
              `}
              <h3>
                ref绑定组件，获取到的就是组件的实例，可以直接调用组件中的方法
                <Context1.Provider value={"我是APP传递过来的数据"}>
                  <Son2 ref={this.sonRef}>
                    <button onClick={this.clickSonFn}>调用子组件的方法</button>
                  </Son2>
                </Context1.Provider>
              </h3>
              <hr />
              Context中的Provider和Consumer (类似vue中的provide/inject)
              祖先组件传递数据给子组件 注意点：
              1.子组件使用父组件创建的context对象，不能自己创建
              1.祖先组件使用React.createContext创建一个Context组件对象，并使用context.Provider包裹子组件，只能通过value属性传递数据
              2.子组件使用祖先组件创建的Context组件对象，并使用Consumer包裹子组件，使作用域插槽的方法获取祖先组件传递过来的数据
              <div className="code-block">
                {`


 //祖先组件
<Context1.Provider value={"我是APP传递过来的数据"}>
  <Son2></Son2>
</Context1.Provider>


 //孙辈组件     Grandchildren.vue            
class Grandchildren extends React.Component {
  render() {
    return (
      <div>
        <div>grandchildren </div>
        <Context1.Consumer>
          {(value) => {
            return <div> 祖先组件传递过来的数据：{value}</div>;
          }}
        </Context1.Consumer>
      </div>
    );
  }
}
              `}
              </div>
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 7 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <hr />
            函数组件 本身的函数体相当于一个render函数可以直接使用 区别：
            1.函数组件没有this 2.函数组件没有生命周期
            3.函数组件通过hooks来完成各种操作 4.函数组件第一个参数为props
            <div className="code-block">
              <Context1.Provider value={"我是APP传递过来的数据"}>
                <FunCom name="我是prop传递的name"></FunCom>
              </Context1.Provider>
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 8 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <HocSon2></HocSon2>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 9 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            React中某个组件更新，会连带着他的子组件一起更新 解决问题: 1.
            源码中通过时间切片与fiber优化 2. 让子组件只做合理的更新
            2.1使用PureComponent：通过PureComponent来优化，PureComponent会浅比较props和state，如果相等则不更新
            2.2使用React.memo包裹组件，生成新组件，页面上使用新组件
            <div>
              父组件的数据{this.state.arr}
              <button
                onClick={() => {
                  this.setState({
                    arr: [...this.state.arr, 1],
                  });
                }}
              >
                arr增加
              </button>
            </div>
            <h3>使用pureComponent</h3>
            <div className="code-block">
              {`
class Optimization extends React.PureComponent {
   render() {
     console.log("Optimization");
     return (
       <div>
         <h1>Optimization</h1>
       </div>
     );
   }
 }
//父組件.js 引入子組件
            <Optimization></Optimization>

                `}
            </div>
            <h3>使用React.memo</h3>
            <div className="code-block">
              {`
//父组件
import Optimization from "./OptimizationCom";
//Optimization为函数组件，使用React.memo包裹，当props、state不变时，不会重新渲染dom

const MemoOptimization = React.memo(Optimization);


<MemoOptimization></MemoOptimization>
                `}
            </div>
            {/* <MemoOptimization></MemoOptimization> */}
            <h4>
              在函数式组件中使用useMemo,useCallback来包裹传递在props上不需要重复声明的对象与方法，这样在父组件更新时，不会由于传递了props导致子组件更新
            </h4>
            <div className="code-block">
              {`
              
let staticInfo = useMemo(() => {
      return { info: "张三" };
    }, []);

    let staticFn = useCallback(() => {
      console.log("staticFn");
    }, []);

<MemoOptimization
              staticInfo={staticInfo}
              staticFn={staticFn}
            ></MemoOptimization>
                `}
              <MemoOptimization></MemoOptimization>
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 10 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <h3>react-route分类:</h3>
            <div>1.react-router-dom 适用于浏览器</div>
            <div>2.react-router-native 适用于react-native</div>
            <div>3.react-router 服务端渲染,适用于ssr</div>
            <h3>router使用步骤：</h3>
            <ul>
              <li>
                <div>
                  使用BrowserRouter 或者 HashRouter 包裹要使用路由的根组件{" "}
                </div>
                tips:一般包裹在App组件上，也可以包裹在任意一个组件上，既可以使用Browser路由，也可以使用hash路由
                <div className="code-block">
                  {`
                  <BrowserRouter>
                        <App />
                  </BrowserRouter>
                  `}
                </div>
              </li>
              <li>
                <div>
                  使用Routes组件，定义路由显示区域，类似与vue的
                  {`<router-view></router-view>`}
                </div>
              </li>
              <li>
                <div>使用Route组件定义规则。相当于vue中的route定义对象</div>
                <div className="code-block">
                  {`
                  <Routes>
                    <Route Component={PageRouter} path="/router"></Route>
                  </Routes>
                `}
                </div>
              </li>
              <li>
                <div>使用NavLink与Link定义跳转链接，类似vue的router-link</div>
                NavLink与Link的区别:
                NavLink定义的链接，当路由匹配时，会给链接添加一个active类，可以自定义样式；
                而Link定义的链接，当路由匹配时，不会添加
                <div className="code-block">
                  {`
                  {this.state.navList.map((item) => {
                    return (
                      <NavLink to={item.path}>
                         {item.name}
                      </NavLink>
                    );
                  })}
                    `}
                </div>
              </li>
            </ul>
            {this.state.navList.map((item) => {
              return (
                <div>
                  <NavLink to={item.path}>{item.name}</NavLink>;
                </div>
              );
            })}
            <Suspense fallback={<div>全局加载中...</div>}>
              <Routes>
                <Route Component={PageRouter} path="/router">
                  {/* 嵌套路由 */}
                  <Route Component={SonRouter} path="son-router" />
                </Route>
                <Route Component={Page3} path="/page3/:id"></Route>
                <Route Component={LazyPage} path="/lazy"></Route>

                {/* token不存在，不能跳转到page2 */}
                <Route
                  path="/page2"
                  element={_token ? <Page2 /> : <Navigate to="/page1" />}
                  // Component={_token ? Page2 : <Navigate to="/page1" />}
                />
                {/* 没有token直接不生成路由 */}
                {/* {_token ? <Route Component={Page2} path="/page2" /> : null} */}
              </Routes>
            </Suspense>

            <hr />
            <h3>其他重要组件</h3>
            <div>
              1. Navigate：重定向组件， 示例：当没有token，会自动跳转到指定路由{" "}
              <div className="cod-block">
                {`
                  //1. token不存在，不能跳转到page2
                <Route
                  path="/page2"
                  element={_token ? <Page2 /> : <Navigate to="/page1" />}
                  // Component={_token ? Page2 : <Navigate to="/page1" />}
                />
                //2. 没有token直接不生成路由
                    {_token ? <Route Component={Page2} path="/page2" /> : null}
                `}
              </div>
            </div>
            <div>
              2.Outlet 嵌套路由的子路由显示出来的地方,类似vue的router-view
              <div className="code-block">
                {`
                <Route Component={PageRouter} path="/router">
                  {/* 嵌套路由 */}
                  <Route Component={SonRouter} path="son-router" />
                </Route>
                // pageRouter组件中
                  export default function Page1() {
                    return (
                      <div>
                        我是router的page页面
                        <Outlet></Outlet>
                      </div>
                    );
                  }
                `}
              </div>
            </div>
            <div>3.动态路由</div>
            <div className="code-block">
              {`
                //id是动态路由参数
                <Route Component={Page3} path="/page3/:id"></Route>

                //page3组件中
              import { useParams, useSearchParams, useLocation } from "react-router-dom";
              export default function Page1() {
                let params = useParams();
                let [searchParams, setSearchParams] = useSearchParams();
                console.log(params);
                console.log(searchParams.get("name")); //类似proxy的取值方法
                setTimeout(() => {
                  setSearchParams({
                    name: "张三",
                    age: 18,
                  });
                }, 2000);
                console.log(useLocation());
                /**
                 * useLocation返回值
                 * hash: "";
                 * key: "qrjio58f";
                 * pathname: "/page3/3";
                 * search: "?name=%E5%BC%A0%E4%B8%89&age=18";
                 * state: null;//不在url上 跳转时传递的参数
                 */

                `}
            </div>
            <hr />
            <h3>useNavigate创建跳转方法，实现页面跳转</h3>
            <div className="code-block">
              {`
const nav = useNavigate();
  //第二个参数，是传递的state
  const goRouter = () => {
    nav("/router?name=张三&age=18", {
      state: {
        reload: true,
      },
    });
  };
                `}
            </div>
            <hr />
            <h3>异步路由：使用lazy方法与suspense组件</h3>
            <div className="code-block">
              {`
                //异步路由
 const LazyPage = lazy(() => import("./views/pages/Lazy"));
 <Suspense fallback={<div>全局加载中...</div>}>
   <Routes>
     <Route Component={LazyPage} path="/lazy"></Route>
   </Routes>
 </Suspense>
                `}
            </div>
          </div>
          <div
            className="state-box"
            style={{
              height: this.state.index === 11 ? "auto" : "0",
              overflow: "hidden",
            }}
          >
            <StoreComponent></StoreComponent>
          </div>
        </div>
      </div>
    );
  }

  componentDidMount() {
    // console.log(this.divRef);
  }
  clickSonFn = () => {
    this.sonRef.current.sayHello();
  };
  propMessage = (msg) => {
    this.setState({
      propSon2Msg: msg,
    });
  };
  handleMessageBySon = (msg) => {
    this.setState({
      message: msg,
    });
  };
  handleCheckChange(e) {
    let checkArray = [...this.state.formData.hobby];
    let value = Number(e.target.value);
    if (e.target.checked) {
      checkArray.push(value);
    } else {
      checkArray = checkArray.filter((item) => item !== value);
    }
    this.setState(
      {
        formData: {
          ...this.state.formData,
          hobby: checkArray,
        },
      },
      () => {
        console.log(this.state.formData.hobby);
      }
    );
  }
  handleArrAdd = () => {
    // PureComponent 对于对象类型，比较的是内存地址，所以使用setState不会触发重新渲染
    //两种方式
    // 1.声明新数组
    let newArr = [...this.state.arr];
    newArr.push(1);
    this.setState({
      arr: newArr,
    });
    // 2.使用扩展运算符
    // this.setState({
    //   arr: [...this.state.arr, 1],
    // });
  };
  handleInputChange(e) {
    this.setState(
      {
        info: {
          ...this.state.info,
          nickName: e.target.value,
        },
      },
      () => {
        console.log("回调函数获取", this.state.info.nickName); // 回调函数获取 张三1
      }
    );
    console.log("直接获取", this.state.info.nickName); //直接获取 张三
  }
  handleExpand(index) {
    if (this.state.index === index) {
      this.setState({
        index: -1,
      });
    } else {
      this.setState({
        index,
      });
    }
  }
  handleClick(e) {
    console.log(e); //  SyntheticBaseEvent 合成的事件对象
    //this默认指向 undefined  处理this指向当前组件 调用方法时绑定this
    console.log(this); //bind绑定后 this指向当前组件 App
    // console.log("按钮被点击了");
  }
  handleArrowFnClick = () => {
    console.log(this);
  };
  handleEventClick(e) {
    console.log("event事件对象", e);
    // SyntheticBaseEvent 是在原生的事件对象上封装的
    // SyntheticBaseEvent.nativeEvent是原生的事件对象
    //阻止冒泡
    e.stopPropagation();
    //阻止默认行为
    e.preventDefault();
  }
  handleParamsClick(...params) {
    //传递了多个参数 最后一个参数为事件对象
    console.log(params);
  }
}

export default App;
